﻿using JESAI.DistributedLock.Internal;
using System;
using System.Data;
using System.Threading;
using System.Threading.Tasks;

namespace JESAI.DistributedLock.Postgres;

public partial class PostgresDistributedLock
{
    /// <summary>
    /// 尝试使用外部拥有的事务同步获取一个事务范围内的建议锁。使用方法： 
    /// <code>
    ///     var transaction = /* 创建一个数据库事务 */ 
    /// 
    ///     var isLockAcquired = myLock.TryAcquireWithTransaction(..., transaction, ...)
    ///
    ///     if (isLockAcquired != null) 
    ///     { 
    ///         /* 我们获取到了锁！ */ 
    ///         
    ///         // 提交或回滚事务，这将释放锁
    ///     }
    /// </code>
    /// 
    /// 注意：事务的所有者负责事务的提交或回滚，以释放获取到的锁。
    /// </summary>
    /// <param name="key">用于获取锁的Postgres建议锁键。</param>
    /// <param name="transaction">用于获取锁的外部拥有的事务。事务的所有者必须提交或回滚事务以释放锁。</param>
    /// <param name="timeout">获取锁的尝试超时时间。默认为0。</param>
    /// <param name="cancellationToken">用于取消等待操作的令牌</param>
    /// <returns>是否成功获取到锁</returns>
    public static bool TryAcquireWithTransaction(PostgresAdvisoryLockKey key, IDbTransaction transaction, TimeSpan timeout = default, CancellationToken cancellationToken = default) =>
        SyncViaAsync.Run(state => TryAcquireWithTransactionAsyncInternal(state.key, state.transaction, state.timeout, state.cancellationToken), (key, transaction, timeout, cancellationToken));

    /// <summary>
    /// 使用外部拥有的事务同步获取一个事务范围内的建议锁，如果尝试超时则抛出<see cref="TimeoutException"/>。使用方法： 
    /// <code>
    ///     var transaction = /* 创建一个数据库事务 */ 
    /// 
    ///     myLock.AcquireWithTransaction(..., transaction, ...)
    ///
    ///     /* 我们获取到了锁！ */ 
    ///         
    ///     // 提交或回滚事务，这将释放锁
    /// </code>
    /// 
    /// 注意：事务的所有者负责事务的提交或回滚，以释放获取到的锁。
    /// </summary>
    /// <param name="key">用于获取锁的Postgres建议锁键。</param>
    /// <param name="transaction">用于获取锁的外部拥有的事务。事务的所有者必须提交或回滚事务以释放锁。</param>
    /// <param name="timeout">获取锁的尝试超时时间。默认为<see cref="Timeout.InfiniteTimeSpan"/></param>
    /// <param name="cancellationToken">用于取消等待操作的令牌</param>
    public static void AcquireWithTransaction(PostgresAdvisoryLockKey key, IDbTransaction transaction, TimeSpan? timeout = null, CancellationToken cancellationToken = default) =>
        SyncViaAsync.Run(state => AcquireWithTransactionAsyncInternal(state.key, state.transaction, state.timeout, state.cancellationToken), (key, transaction, timeout, cancellationToken));

    /// <summary>
    /// 使用外部拥有的事务异步尝试获取一个事务范围内的建议锁。使用方法： 
    /// <code>
    ///     var transaction = /* 创建一个数据库事务 */ 
    /// 
    ///     var isLockAcquired = await myLock.TryAcquireWithTransactionAsync(..., transaction, ...)
    ///
    ///     if (isLockAcquired) 
    ///     { 
    ///         /* 我们获取到了锁！ */ 
    ///         
    ///         // 提交或回滚事务，这将释放锁
    ///     }
    /// </code>
    /// 
    /// 注意：事务的所有者负责事务的提交或回滚，以释放获取到的锁。
    /// </summary>
    /// <param name="key">用于获取锁的Postgres建议锁键。</param>
    /// <param name="transaction">用于获取锁的外部拥有的事务。事务的所有者必须提交或回滚事务以释放锁。</param>
    /// <param name="timeout">获取锁的尝试超时时间。默认为0。</param>
    /// <param name="cancellationToken">用于取消等待操作的令牌</param>
    /// <returns>是否成功获取到锁</returns>
    public static ValueTask<bool> TryAcquireWithTransactionAsync(PostgresAdvisoryLockKey key, IDbTransaction transaction, TimeSpan timeout = default, CancellationToken cancellationToken = default) =>
        TryAcquireWithTransactionAsyncInternal(key, transaction, timeout, cancellationToken);

    /// <summary>
    /// 使用外部拥有的事务异步获取一个事务范围内的建议锁，如果尝试超时则抛出<see cref="TimeoutException"/>。使用方法： 
    /// <code>
    ///     var transaction = /* 创建一个数据库事务 */ 
    /// 
    ///     await myLock.AcquireWithTransactionAsync(..., transaction, ...)
    ///
    ///     /* 我们获取到了锁！ */ 
    ///         
    ///     // 提交或回滚事务，这将释放锁
    /// </code>
    /// 
    /// 注意：事务的所有者负责事务的提交或回滚，以释放获取到的锁。
    /// </summary>
    /// <param name="key">用于获取锁的Postgres建议锁键。</param>
    /// <param name="transaction">用于获取锁的外部拥有的事务。事务的所有者必须提交或回滚事务以释放锁。</param>
    /// <param name="timeout">获取锁的尝试超时时间。默认为<see cref="Timeout.InfiniteTimeSpan"/></param>
    /// <param name="cancellationToken">用于取消等待操作的令牌</param>
    public static ValueTask AcquireWithTransactionAsync(PostgresAdvisoryLockKey key, IDbTransaction transaction, TimeSpan? timeout = null, CancellationToken cancellationToken = default) =>
        AcquireWithTransactionAsyncInternal(key, transaction, timeout, cancellationToken);

    internal static ValueTask<bool> TryAcquireWithTransactionAsyncInternal(PostgresAdvisoryLockKey key, IDbTransaction transaction, TimeSpan timeout, CancellationToken cancellationToken)
    {
        if (key == null) { throw new ArgumentNullException(nameof(key)); }
        if (transaction == null) { throw new ArgumentNullException(nameof(transaction)); }

        return TryAcquireAsync();

        async ValueTask<bool> TryAcquireAsync()
        {
            var connection = new PostgresDatabaseConnection(transaction);

            await using (connection.ConfigureAwait(false))
            {
                var lockAcquiredCookie = await PostgresAdvisoryLock.ExclusiveLock.TryAcquireAsync(connection, key.ToString(), timeout, cancellationToken).ConfigureAwait(false);

                return lockAcquiredCookie != null;
            }
        }
    }

    internal static ValueTask AcquireWithTransactionAsyncInternal(PostgresAdvisoryLockKey key, IDbTransaction transaction, TimeSpan? timeout, CancellationToken cancellationToken)
    {
        if (key == null) { throw new ArgumentNullException(nameof(key)); }
        if (transaction == null) { throw new ArgumentNullException(nameof(transaction)); }

        return AcquireAsync();

        async ValueTask AcquireAsync()
        {
            var connection = new PostgresDatabaseConnection(transaction);
            await using (connection.ConfigureAwait(false))
            {
                await PostgresAdvisoryLock.ExclusiveLock.TryAcquireAsync(connection, key.ToString(), timeout, cancellationToken).ThrowTimeoutIfNull().ConfigureAwait(false);
            }
        }
    }
}
